/*
 * Copyright (c) 2005, 2014 vacoor
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 */
package org.vacoor.nothing.web.service.support;

/*
import freemarker.template.Configuration;
import freemarker.template.Template;
import jxl.write.Label;
import jxl.write.WritableCell;
import jxl.write.WritableWorkbook;
import jxl.write.WritableSheet;
import jxl.write.WriteException;
import jxl.write.biff.RowsExceededException;
*/

import jxl.write.*;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.springframework.beans.factory.annotation.Value;
import org.vacoor.nothing.common.util.StringUtils;
import org.vacoor.nothing.web.domain.Filters;
import org.vacoor.nothing.web.domain.Pageable;
import org.vacoor.nothing.web.domain.support.PageRequest;
import org.vacoor.nothing.web.service.ExcelService;
import org.vacoor.nothing.web.service.ServiceException;

import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import java.lang.Boolean;
import java.lang.Number;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

/**
 * excel 导出
 * <p/>
 * 这里实现为一次查询出所有数据, 应该不会有很变态的数据量导出吧?!,
 * 如果有请分页导出
 *
 * @author vacoor
 */
public abstract class ExcelServiceImpl<E, ID extends Serializable> extends CrudServiceImpl<E, ID> implements ExcelService<E, ID> {
    private static final int DEFAULT_EXPORT_MAX_SIZE = 1048576 - 1; // 2007 最大行数 - 1
    /*
    private static final String DEFAULT_EXCEL2003_XML_TEMPLATE = "excel2003xml.mini.ftl";

    @Autowired
    protected Configuration freeMarkerConfiguration;
    private String excel2003XmlTemplate = DEFAULT_EXCEL2003_XML_TEMPLATE;
    */
    @Value("${exportLimitSize:" + DEFAULT_EXPORT_MAX_SIZE + "}")
    protected int exportMaxSize;


    /**
     * 使用 POI HSSF 导出 excel 2003
     *
     * @param filters
     * @param pageable
     * @param out
     * @throws Exception
     */
    @Override
    public void exportExcel2003(Filters filters, Pageable pageable, OutputStream out) throws ServiceException {
        HSSFWorkbook workbook = new HSSFWorkbook();
        exportExcelUsePoi(filters, pageable, workbook, null, out);
    }

    /**
     * 使用 POI SXSSF 导出 excel 2007
     *
     * @param filters
     * @param pageable
     * @param out
     * @throws Exception
     */
    @Override
    public void exportExcel2007(Filters filters, Pageable pageable, OutputStream out) throws ServiceException {
        SXSSFWorkbook workbook = new SXSSFWorkbook();
        exportExcelUsePoi(filters, pageable, workbook, null, out);
        // workbook.dispose();
    }

    /**
     * 将 filters, pageable 查询的数据使用给定的workbook 写入输出流
     */
    protected void exportExcelUsePoi(Filters filters, Pageable pageable, Workbook workbook, String sheetName, OutputStream out) throws ServiceException {
        try {
            /*
            ensureExportSize(pageable);

            filters = prepareSearchable(filters);
            pageable = preparePageable(pageable);
            */

            List<E> data = null; //getPagingDao().findMany(filters, pageable);

            sheetName = StringUtils.hasText(sheetName) ? sheetName : "Sheet1";
            Sheet sheet = workbook.createSheet(sheetName);
            String[] captions = buildExcelCaptions(data);

            // poi 不像 jxl 会自动设置style, poi 日期必须自行设置style
            CellStyle dateStyle = workbook.createCellStyle();
            dateStyle.setDataFormat(workbook.createDataFormat().getFormat("yyyy-MM-dd"));

            boolean hasCaptions = captions != null && captions.length > 0;
            // 写入标题
            if (hasCaptions) {
                Row row = sheet.createRow(0);
                for (int i = 0; i < captions.length; i++) {
                    row.createCell(i).setCellValue(captions[i]);
                }
                sheet.createFreezePane(0, 1, 0, 1); // 冻结首行
            }
            /*
            sheet.setDefaultColumnStyle(4, dateStyle);
            */

            // 写入行
            for (int r = 0; r < data.size(); r++) {
                E e = data.get(r);
                Object[] values = buildExcelData(e);
                Row row = sheet.createRow(hasCaptions ? r + 1 : r);

                if (values == null) {
                    continue;
                }

                // 写入列
                for (int c = 0; c < values.length; c++) {
                    Object value = values[c];
                    if (value == null) {
                        continue;
                    }

                    Cell cell = row.createCell(c);

                    if (value instanceof Boolean) {
                        cell.setCellValue((Boolean) value);
                    } else if (value instanceof Number) {
                        cell.setCellValue(((Number) value).doubleValue());
                    } else if (value instanceof Date) {
                        cell.setCellValue((Date) value);
                        cell.setCellStyle(dateStyle);
                    } else if (value instanceof Calendar) {
                        cell.setCellValue((Calendar) value);
                        cell.setCellStyle(dateStyle);
                    } else {
                        // cell.setCellType(Cell.CELL_TYPE_STRING);
                        cell.setCellValue(value.toString());
                    }
                }
            }

            workbook.write(out);
            System.gc();
        } catch (Exception e) {
            // TODO 这里的异常是否可以忽略
            throw new ServiceException("导出失败");
        }
    }

    protected void ensureExportSize(Pageable pageable) {
        if (pageable.getPageSize() > exportMaxSize) {
            ((PageRequest) pageable).setPageSize(exportMaxSize);
        }
    }


    // --------------------

    /**
     * 使用 jxl 导出 excel 2003, 数据量小时, jxl 效率要比 poi 高的多
     *
     * @param filters
     * @param pageable
     * @param out
     * @throws Exception
     */
    public void exportExcel2003UseJxl(Filters filters, Pageable pageable, OutputStream out) {
        try {
//            filters = prepareSearchable(filters);
//            pageable = preparePageable(pageable);

            ensureExportSize(pageable); // 矫正导出大小

            List<E> data = null; //getPagingDao().findMany(filters, pageable);

            String sheetName = null;
            sheetName = StringUtils.hasText(sheetName) ? sheetName : "Sheet1";
            String[] captions = buildExcelCaptions(data);

            WritableWorkbook workbook = jxl.Workbook.createWorkbook(out);
            WritableSheet sheet = workbook.createSheet(sheetName, 0);
            // 日期格式化, 使用默认的就好了
            // jxl.write.WritableCellFormat dateFmt = new jxl.write.WritableCellFormat(new jxl.write.DateFormat("yyyy/MM/dd"));

            // 标题
            boolean hasCaptions = captions != null && captions.length > 0;
            if (hasCaptions) {
                for (int c = 0; c < captions.length; c++) {
                    Label label = new Label(c, 0, captions[c]);
                    sheet.addCell(label);
                }
                sheet.getSettings().setVerticalFreeze(1); // 冻结前一行, 参数为冻结前几行
            }
            /*
            CellView cellView = new CellView();
            cellView.setSize(100 * 256);
            cellView.setFormat(dateFmt);
            sheet.setColumnView(4, cellView);
            */

            // 行
            for (int r = 0; r < data.size(); r++) {
                E e = data.get(r);
                if (e == null) {
                    continue;
                }

                // 列
                Object[] values = buildExcelData(e);
                int rn = hasCaptions ? r + 1 : r;

                for (int c = 0; c < values.length; c++) {
                    Object value = values[c];
                    if (value == null) {
                        continue;
                    }

                    WritableCell cell;


                    if (value instanceof Boolean) {
                        cell = new jxl.write.Boolean(c, rn, (Boolean) value);
                    } else if (value instanceof Number) {
                        cell = new jxl.write.Number(c, rn, ((Number) value).doubleValue());
                    } else if (value instanceof Date) {
                        cell = new jxl.write.DateTime(c, rn, (Date) value);
                        // cell.setCellFormat(dateFmt);
                    } else if (value instanceof Calendar) {
                        cell = new jxl.write.DateTime(c, rn, ((Calendar) value).getTime());
                        // cell.setCellFormat(dateFmt);
                    } else {
                        cell = new Label(c, rn, values[c].toString());
                    }

                    sheet.addCell(cell);
                }
            }

            workbook.write();
            workbook.close();
            System.gc();
        } catch (WriteException e) {
            throw new ServiceException("导出失败");
        } catch (IOException e) {
            throw new ServiceException("导出失败");
        }
    }

    /**
     * 使用 Freemarker 来渲染模版， 导出 excel 2003 xml
     * 注: 每个实体中都应该包含getData 来提供要导出的数据
     * Freemarker 是边渲染边输出, 数据量大时响应会快点
     * 之后可以自己来手写直接字符串写入， 效率是不是会更高??
     *
     * @param filters
     * @param pageable
     * @param out
     */
    /*
    public void exportExcel2003Xml(Keywords filters, Pageable pageable, OutputStream out) throws Exception {
        try {
            filters = prepareSearchable(filters);
            pageable = preparePageable(pageable);

            ensureExportSize(pageable);

            List<E> section = getPagingDao().findMany(filters, pageable);

            Template template = freeMarkerConfiguration.getTemplate(excel2003XmlTemplate);

            Map<String, Object> model = new LinkedHashMap<String, Object>();
            model.put("sheetName", "sheet");
            model.put("captions", buildExcelCaptions(section));
            // section 中每个元素都要提供 getData 方法返回需要导出的数据, freemarker 不支持输出 boolean
            model.put("content", section);

            OutputStreamWriter writer = new OutputStreamWriter(out);
            template.process(model, writer);
            writer.flush();
        } catch (Exception e) {
            throw new ServiceException(e);
        }
        System.gc();
    }
    */

    /*
    public void exportCsv(Keywords filters, Pageable pageable, OutputStream out) throws Exception {
        throw new UnsupportedOperationException("暂时不支持导出 csv");
    }
    */

    /**
     * 为导出数据构建标题
     *
     * @param data
     * @return
     */
    protected abstract String[] buildExcelCaptions(List<E> data);

    /**
     * 将导出对象转换为 cell 数组
     *
     * @param e
     * @return
     */
    protected abstract Object[] buildExcelData(E e);

    public int getExportMaxSize() {
        return exportMaxSize;
    }

    public void setExportMaxSize(int exportMaxSize) {
        this.exportMaxSize = exportMaxSize;
    }
}
